/**
 * Project: apollo-base-tools
 * 
 * File Created at 2017年8月2日
 * 
 * Copyright 2015-2016 dx.com Croporation Limited.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * DongXue software Company. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with dx.com.
 */
package org.apollo.base.tools.ui.view;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Composite;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import javax.swing.text.View;

import org.apollo.swing.util.UIResourceManager;
import org.apollo.swing.util.UIUtil;
import sun.swing.SwingUtilities2;
/**
 * @ClassName: JDynamicTabbedPane
 * @Description: 动态也签面板
 * @author wuzhenfang(wzfbj2008@163.com)
 * @date 2017年8月2日 上午9:37:23
 * @version V1.0
 */
public class JDynamicTabbedPane extends JTabbedPane {

	private static final long serialVersionUID = 5910424090107050522L;

	private double scaleRatio = 0.3;// 获取缩放比例

	private int tabHeight;

	private Color disabledForeground;

	private Map<Component, Color> disabledForegroundMap;

	private LinkedList<Boolean> closeable = new LinkedList<Boolean>();// 可关闭的链表

	private Map<String, TabbedModel> tabbedmaps = new ConcurrentHashMap<String, TabbedModel>();

	private Map<String, Component> maps = new ConcurrentHashMap<String, Component>();

	public JDynamicTabbedPane() {
		setAutoscrolls(true);
		setUI(new TabbedPaneUI());
		setForeground(new Color(0, 28, 48));
		// setBackground(Color.GRAY);
		setBorder(new EmptyBorder(0, 0, 0, 0));
		setOpaque(false);
		setTabLayoutPolicy(SCROLL_TAB_LAYOUT);
		tabHeight = 30;
		disabledForeground = new Color(123, 123, 122);
		disabledForegroundMap = new HashMap<Component, Color>();
	}

	public void addTab(String title, Component component) {
		this.addTab(title, null, component, null, true);
	}

	public void addTab(String title, Icon icon, Component component) {
		this.addTab(title, icon, component, null, true);
	}

	public Color getDisabledForegroundAt(int tabIndex) {
		Component c = getComponentAt(tabIndex);
		Color color = c == null ? null : disabledForegroundMap.get(c);
		return color == null ? disabledForeground : color;
	}

	/**
	 * 加入组件
	 * @param title 标题
	 * @param icon 图标
	 * @param component 组件
	 * @param tip 提示信息
	 * @param closabel 是否可关闭
	 */
	public void addTab(String title, Icon icon, Component component, String tip, boolean closable) {
		if (!tabbedmaps.containsKey(title)) {
			addTab(title, icon, component, tip);
			tabbedmaps.put(title, new TabbedModel(title, closable, icon, tip, component));
			closeable.add(closable);
			maps.put(tip, component);
		}
	}

	public void insertTab(String title, Component component, Icon icon, String tip, int index) {
		tip = "tab" + component.hashCode();
		maps.put(tip, component);
		super.insertTab(title, icon, component, tip, index);
	}

	public void removeTabAt(int index) {
		closeable.remove(index);
		Component component = getComponentAt(index);
		String title = null;
		for (Iterator<Entry<String, Component>> itr = maps.entrySet().iterator(); itr.hasNext();) {
			Entry<String, Component> entry = itr.next();
			if (entry.getValue() == component) {
				title = entry.getKey();
			}
		}
		tabbedmaps.remove(title);
		maps.remove("tab" + component.hashCode());
		super.removeTabAt(index);
	}

	public double getScaleRatio() {
		return scaleRatio;
	}

	public void setScaleRatio(double scaleRatio) {
		this.scaleRatio = scaleRatio;
	}

	public int getTabHeight() {
		return tabHeight;
	}

	public void setTabHeight(int tabHeight) {
		this.tabHeight = tabHeight;
		this.revalidate();
	}

	/**
	 * @ClassName: TabbedPaneUI
	 * @Description:UI
	 * @author wuzhenfang(wzfbj2008@163.com)
	 * @date 2017年8月2日 下午3:17:28
	 * @version V1.0
	 */
	class TabbedPaneUI extends BasicTabbedPaneUI {
		private Rectangle[] closeRects = new Rectangle[0];

		private int nowIndex = -1;

		private int oldIndex = -1;

		public TabbedPaneUI() {
			super();
			initialize();
		}

		@Override
		protected LayoutManager createLayoutManager() {
			return new TabbedPaneLayout();
		}

		protected int calculateTabHeight(int tabPlacement, int tabIndex, int fontHeight) {
			if (tabPane instanceof JDynamicTabbedPane) {
				int tabHeight = ((JDynamicTabbedPane) tabPane).getTabHeight();
				if (tabHeight >= 0) {
					return tabHeight;
				}
			}
			return super.calculateTabHeight(tabPlacement, tabIndex, fontHeight);
		}

		private void initialize() {

			/*
			 * tabRunOverlay = 2; textIconGap = 5; tabInsets = new Insets(1, 9, 1, 9); selectedTabPadInsets = new Insets(2, 2, 2, 1); contentBorderInsets = new Insets(0, 0, 0, 0);
			 * tabAreaInsets = new Insets(0, 0, 0, 0);
			 */

			addMouseListener(new MouseAdapter() {
				public void mousePressed(MouseEvent e) {
					for (int i = 0; i < getTabCount(); i++) {
						if (closeRects[i].contains(e.getPoint()) && closeable.get(i)) {
							removeTabAt(i);
						}
					}
				}
			});
			addMouseMotionListener(new MouseAdapter() {
				public void mouseMoved(MouseEvent e) {
					nowIndex = -1;
					for (int i = 0; i < getTabCount(); i++) {
						if (closeRects[i].contains(e.getPoint()) && closeable.get(i)) {
							nowIndex = i;
							break;
						}
					}
					if (oldIndex != nowIndex) {
						if (nowIndex != -1) {
							repaint(closeRects[nowIndex]);// 控制重绘区域
						} else {
							if (oldIndex < getTabCount()) {
								repaint(closeRects[oldIndex]);// 控制重绘区域
							}
						}
						oldIndex = nowIndex;
					}
				}
			});
		}

		public void paint(Graphics g, JComponent c) {
			paintTabBarBackground(g);
			super.paint(g, c);
		}

		@Override
		protected void paintTab(Graphics g, int tabPlacement, Rectangle[] rects, int tabIndex, Rectangle iconRect, Rectangle textRect) {
			super.paintTab(g, tabPlacement, rects, tabIndex, iconRect, textRect);
			if (closeable.get(tabIndex)) {// && (tabIndex == getSelectedIndex())判断是否选中
				paintCloseIcon(g, tabIndex, tabIndex == nowIndex);
			}
		}

		private void paintCloseIcon(Graphics g, int tabIndex, boolean entered) {
			Rectangle rect = closeRects[tabIndex];
			int x = rect.x;
			int y = rect.y;
			int[] xs = { x, x + 2, x + 4, x + 5, x + 7, x + 9, x + 9, x + 7, x + 7, x + 9, x + 9, x + 7, x + 5, x + 4, x + 2, x, x, x + 2, x + 2, x };
			int[] ys = { y, y, y + 2, y + 2, y, y, y + 2, y + 4, y + 5, y + 7, y + 9, y + 9, y + 7, y + 7, y + 9, y + 9, y + 7, y + 5, y + 4, y + 2 };

			if (entered) {
				g.setColor(new Color(252, 160, 160));
				setCursor(new Cursor(Cursor.HAND_CURSOR));
			} else {
				g.setColor(Color.GRAY);
				setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
			}

			g.fillPolygon(xs, ys, 20);
			g.drawPolygon(xs, ys, 20);
		}

		@Override
		protected void layoutLabel(int tabPlacement, FontMetrics metrics, int tabIndex, String title, Icon icon, Rectangle tabRect, Rectangle iconRect, Rectangle textRect,
				boolean isSelected) {
			textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
			View v = getTextViewForTab(tabIndex);
			if (v != null) {
				tabPane.putClientProperty("html", v);
			}
			SwingUtilities.layoutCompoundLabel((JComponent) tabPane, metrics, title, icon, SwingUtilities.CENTER, SwingUtilities.CENTER, SwingUtilities.CENTER,
					SwingUtilities.TRAILING, tabRect, iconRect, textRect, textIconGap);
			tabPane.putClientProperty("html", null);
		}

		@Override
		protected void assureRectsCreated(int tabCount) {
			super.assureRectsCreated(tabCount);
			int rectArrayLen = closeRects.length;
			if (tabCount != rectArrayLen) {
				Rectangle[] tempRectArray = new Rectangle[tabCount];
				System.arraycopy(closeRects, 0, tempRectArray, 0, Math.min(rectArrayLen, tabCount));
				closeRects = tempRectArray;
				for (int rectIndex = rectArrayLen; rectIndex < tabCount; rectIndex++) {
					closeRects[rectIndex] = new Rectangle();
				}
			}
		}

		// =============================
		protected void paintTabBackground(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
			Image image = null;
			boolean tabEnabled = tabPane.isEnabledAt(tabIndex) && tabPane.isEnabled();

			if (isSelected) {
				image = UIResourceManager.getImage(UIResourceManager.KEY_TABBED_PANE_PRESSED_IMAGE);
			} else if (getRolloverTab() == tabIndex && tabEnabled) {
				image = UIResourceManager.getImage(UIResourceManager.KEY_TABBED_PANE_ROLLOVER_IMAGE);
			}

			if (image != null) {
				Graphics2D g2d = (Graphics2D) g;
				Composite oldComposite = g2d.getComposite();

				if (!tabEnabled) {
					g2d.setComposite(AlphaComposite.SrcOver.derive(0.5f));
				}

				UIUtil.paintImage(g, image, new Insets(3, 1, 1, 1), new Rectangle(x, y, w, h), tabPane);
				g2d.setComposite(oldComposite);
			}

			if (!tabPane.isEnabledAt(tabIndex) && tabPane.isEnabled() && !isSelected) {
				g.setColor(new Color(255, 255, 255, 127));
				g.fillRect(x, y, w, h);
			}
		}

		protected void paintTabBarBackground(Graphics g) {
			Rectangle rect = null;
			Insets insets = tabPane.getInsets();
			int tabPlacement = tabPane.getTabPlacement();
			int width = tabPane.getWidth();
			int height = tabPane.getHeight();
			int tabBarWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
			int tabBarHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);

			switch (tabPlacement) {
			case JTabbedPane.BOTTOM: {
				rect = new Rectangle(insets.left, height - insets.bottom - tabBarHeight, width - insets.left - insets.right, tabBarHeight);
				break;
			}
			case JTabbedPane.LEFT: {
				rect = new Rectangle(insets.left, insets.top, tabBarWidth, height - insets.top - insets.bottom);
				break;
			}
			case JTabbedPane.RIGHT: {
				rect = new Rectangle(width - insets.right - tabBarWidth, insets.top, tabBarWidth, height - insets.top - insets.bottom);
				break;
			}
			case JTabbedPane.TOP:
			default: {
				rect = new Rectangle(insets.left, insets.top, width - insets.left - insets.right, tabBarHeight);
			}
			}

			Graphics2D g2d = (Graphics2D) g;
			Composite oldComposite = g2d.getComposite();

			if (!tabPane.isEnabled()) {
				g2d.setComposite(AlphaComposite.SrcOver.derive(0.5f));
			}

			UIUtil.paintImage(g, UIResourceManager.getImage(UIResourceManager.KEY_TABBED_PANE_BACDGROUND_IMAGE), new Insets(1, 1, 1, 1), rect, tabPane);
			g2d.setComposite(oldComposite);
		}

		/*
		 * protected void paintTabBorder(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) { int rolloverTab = getRolloverTab(); boolean
		 * tabEnabled = tabPane.isEnabledAt(tabIndex) && tabPane.isEnabled();
		 * 
		 * if ((rolloverTab == tabIndex && tabEnabled) || isSelected) { return; }
		 * 
		 * Image image; Graphics2D g2d = (Graphics2D) g; Composite oldComposite = g2d.getComposite();
		 * 
		 * if (!tabEnabled) { g2d.setComposite(AlphaComposite.SrcOver.derive(0.5f)); }
		 * 
		 * if (tabPlacement == JTabbedPane.LEFT || tabPlacement == JTabbedPane.RIGHT) { image = UIResourceManager.getImage(UIResourceManager.KEY_TABBED_PANE_SPLIT_H_IMAGE);
		 * UIUtil.paintImage(g, image, new Insets(2, 1, 2, 1), new Rectangle(x, y + (h - 1), w, 2), tabPane); } else { image =
		 * UIResourceManager.getImage(UIResourceManager.KEY_TABBED_PANE_SPLIT_V_IMAGE); UIUtil.paintImage(g, image, new Insets(2, 1, 2, 1), new Rectangle(x + (w - 2), y, 2, h),
		 * tabPane); }
		 * 
		 * g2d.setComposite(oldComposite); }
		 */

		protected void paintText(Graphics g, int tabPlacement, Font font, FontMetrics metrics, int tabIndex, String title, Rectangle textRect, boolean isSelected) {
			g.setFont(font);
			View view = getTextViewForTab(tabIndex);

			if (view != null) {
				view.paint(g, textRect);
			} else {
				int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);

				if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) {
					g.setColor(tabPane.getForegroundAt(tabIndex));
					SwingUtilities2.drawStringUnderlineCharAt(tabPane, g, title, mnemIndex, textRect.x-10, textRect.y + metrics.getAscent());
				} else {
					if (tabPane instanceof JDynamicTabbedPane) {
						g.setColor(((JDynamicTabbedPane) tabPane).getDisabledForegroundAt(tabIndex));
						SwingUtilities2.drawStringUnderlineCharAt(tabPane, g, title, mnemIndex, textRect.x-10, textRect.y + metrics.getAscent());
					} else {
						super.paintText(g, tabPlacement, font, metrics, tabIndex, title, textRect, isSelected);
					}
				}
			}
		}

		protected void paintIcon(Graphics g, int tabPlacement, int tabIndex, Icon icon, Rectangle iconRect, boolean isSelected) {
			if (icon != null) {
				Graphics2D g2d = (Graphics2D) g;
				Composite oldComposite = g2d.getComposite();

				if (!tabPane.isEnabled() || !tabPane.isEnabledAt(tabIndex)) {
					g2d.setComposite(AlphaComposite.SrcOver.derive(0.5f));
				}

				icon.paintIcon(tabPane, g, iconRect.x - 12, iconRect.y);
				g2d.setComposite(oldComposite);
			}
		}
		// =============================
		
		/**
		 * @ClassName: TabbedPaneLayout
		 * @Description: 页签布局
		 * @author wuzhenfang(wzfbj2008@163.com)
		 * @date 2017年8月5日 上午10:09:02
		 * @version V1.0
		 */
		class TabbedPaneLayout extends BasicTabbedPaneUI.TabbedPaneLayout {
			@Override
			protected void calculateTabRects(int tabPlacement, int tabCount) {
				FontMetrics metrics = getFontMetrics();
				Dimension size = tabPane.getSize();
				Insets insets = tabPane.getInsets();
				Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
				int fontHeight = metrics.getHeight();
				int selectedIndex = tabPane.getSelectedIndex();
				int tabRunOverlay;
				int i, j;
				int x, y;
				int returnAt;
				boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
				boolean leftToRight = true;

				switch (tabPlacement) {
				case LEFT:
					maxTabWidth = calculateMaxTabWidth(tabPlacement) + 12;
					x = insets.left + tabAreaInsets.left;
					y = insets.top + tabAreaInsets.top;
					returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
					break;
				case RIGHT:
					maxTabWidth = calculateMaxTabWidth(tabPlacement) + 12;
					x = size.width - insets.right - tabAreaInsets.right - maxTabWidth;
					y = insets.top + tabAreaInsets.top;
					returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
					break;
				case BOTTOM:
					maxTabHeight = calculateMaxTabHeight(tabPlacement);
					x = insets.left + tabAreaInsets.left;
					y = size.height - insets.bottom - tabAreaInsets.bottom - maxTabHeight;
					returnAt = size.width - (insets.right + tabAreaInsets.right);
					break;
				case TOP:
				default:
					maxTabHeight = calculateMaxTabHeight(tabPlacement);
					x = insets.left + tabAreaInsets.left;
					y = insets.top + tabAreaInsets.top;
					returnAt = size.width - (insets.right + tabAreaInsets.right);
					break;
				}

				tabRunOverlay = getTabRunOverlay(tabPlacement);
				runCount = 0;
				selectedRun = -1;
				if (tabCount == 0) {
					return;
				}
				selectedRun = 0;
				runCount = 1;
				Rectangle rect;

				for (i = 0; i < tabCount; i++) {
					rect = rects[i];
					if (!verticalTabRuns) {
						// Tabs on TOP or BOTTOM....
						if (i > 0) {
							rect.x = rects[i - 1].x + rects[i - 1].width;
						} else {
							tabRuns[0] = 0;
							runCount = 1;
							maxTabWidth = 0;
							rect.x = x;
						}
						rect.width = calculateTabWidth(tabPlacement, i, metrics) + 12;
						maxTabWidth = Math.max(maxTabWidth, rect.width);
						if (rect.x != 2 + insets.left && rect.x + rect.width > returnAt) {
							if (runCount > tabRuns.length - 1) {
								expandTabRunsArray();
							}
							tabRuns[runCount] = i;
							runCount++;
							rect.x = x;
						}
						rect.y = y;
						rect.height = maxTabHeight/* - 2 */;

					} else {
						// Tabs on LEFT or RIGHT...
						if (i > 0) {
							rect.y = rects[i - 1].y + rects[i - 1].height;
						} else {
							tabRuns[0] = 0;
							runCount = 1;
							maxTabHeight = 0;
							rect.y = y;
						}
						rect.height = calculateTabHeight(tabPlacement, i, fontHeight);// 页签上部的高度
						maxTabHeight = Math.max(maxTabHeight, rect.height);
						if (rect.y != 2 + insets.top && rect.y + rect.height > returnAt) {
							if (runCount > tabRuns.length - 1) {
								expandTabRunsArray();
							}
							tabRuns[runCount] = i;
							runCount++;
							rect.y = y;
						}
						rect.x = x;
						rect.width = maxTabWidth/* - 2 */;
					}
					if (i == selectedIndex) {
						selectedRun = runCount - 1;
					}
				}
				if (runCount > 1) {
					normalizeTabRuns(tabPlacement, tabCount, verticalTabRuns ? y : x, returnAt);
					selectedRun = getRunForTab(tabCount, selectedIndex);
					if (shouldRotateTabRuns(tabPlacement)) {
						rotateTabRuns(tabPlacement, selectedRun);
					}
				}

				for (i = runCount - 1; i >= 0; i--) {
					int start = tabRuns[i];
					int next = tabRuns[i == (runCount - 1) ? 0 : i + 1];
					int end = (next != 0 ? next - 1 : tabCount - 1);
					if (!verticalTabRuns) {
						for (j = start; j <= end; j++) {
							rect = rects[j];
							rect.y = y;
							rect.x += getTabRunIndent(tabPlacement, i);
						}
						if (shouldPadTabRun(tabPlacement, i)) {
							padTabRun(tabPlacement, start, end, returnAt);
						}
						if (tabPlacement == BOTTOM) {
							y -= (maxTabHeight - tabRunOverlay);
						} else {
							y += (maxTabHeight - tabRunOverlay);
						}
					} else {
						for (j = start; j <= end; j++) {
							rect = rects[j];
							rect.x = x;
							rect.y += getTabRunIndent(tabPlacement, i);
						}
						if (shouldPadTabRun(tabPlacement, i)) {
							padTabRun(tabPlacement, start, end, returnAt);
						}
						if (tabPlacement == RIGHT) {
							x -= (maxTabWidth - tabRunOverlay);
						} else {
							x += (maxTabWidth - tabRunOverlay);
						}
					}
				}

				if (!leftToRight && !verticalTabRuns) {
					int rightMargin = size.width - (insets.right + tabAreaInsets.right);
					for (i = 0; i < tabCount; i++) {
						rects[i].x = rightMargin - rects[i].x - rects[i].width;
					}
				}

				for (i = 0; i < tabCount; i++) {
					closeRects[i].x = rects[i].x + rects[i].width - 20;
					closeRects[i].y = rects[i].y + 10;
					closeRects[i].width = 10;
					closeRects[i].height = 10;
				}

			}
		}
	}
}

/**
 * @ClassName: TabbedModel
 * @Description: 页签模型
 * @author wuzhenfang(wzfbj2008@163.com)
 * @date 2017年8月2日 上午11:20:01
 * @version V1.0
 */
class TabbedModel {

	private String title;

	private boolean closeable;// 能否关闭

	private Component panel;//

	private Icon fileIcon;//

	private String tip;//

	public TabbedModel(String title, boolean closeable, Icon fileIcon, String tip, Component panel) {
		this.title = title;
		this.closeable = closeable;
		this.panel = panel;
		this.panel = panel;
		this.tip = tip;
	}

	/**
	 * @return the title
	 */
	public String getTitle() {
		return title;
	}

	/**
	 * @param title the title to set
	 */
	public void setTitle(String title) {
		this.title = title;
	}

	/**
	 * @return the closeable
	 */
	public boolean isCloseable() {
		return closeable;
	}

	/**
	 * @param closeable the closeable to set
	 */
	public void setCloseable(boolean closeable) {
		this.closeable = closeable;
	}

	/**
	 * @return the panel
	 */
	public Component getPanel() {
		return panel;
	}

	/**
	 * @param panel the panel to set
	 */
	public void setPanel(Component panel) {
		this.panel = panel;
	}

	/**
	 * @return the fileIcon
	 */
	public Icon getFileIcon() {
		return fileIcon;
	}

	/**
	 * @param fileIcon the fileIcon to set
	 */
	public void setFileIcon(Icon fileIcon) {
		this.fileIcon = fileIcon;
	}

	/**
	 * @return the tip
	 */
	public String getTip() {
		return tip;
	}

	/**
	 * @param tip the tip to set
	 */
	public void setTip(String tip) {
		this.tip = tip;
	}

}
